//
// Created by entaoyang@163.com on 2017/10/18.
// Copyright (c) 2017 yet.net. All rights reserved.
//

import Foundation
#if os(iOS) || os(tvOS)
import UIKit
#else
import AppKit
#endif

//TODO 高宽 三中情况, 1:固定值; 2:依赖子view; 3:依赖父View
//统一 View.layoutWidth/Height
//这样在sizeThatFit返回时, 可以根据这三种情况来处理.  固定值, 直接返回固定高宽的size, 依赖子View返回contentSize, 依赖父View返回当前frame的size

public let SelfViewName = "."
public let ParentViewName = ".."
public let MatchParent: CGFloat = -1

public let WrapContent: CGFloat = -2

public class ControlSizes {
    public static let textHeight: CGFloat = 30
    public static let editHeight: CGFloat = 42
    public static let buttonHeight: CGFloat = 42
}

extension CGFloat {
    var geZero: CGFloat {
        if self < 0 {
            return 0
        }
        return self
    }
}


public protocol OtherView {
}

extension View: OtherView {
}

extension String: OtherView {
}

extension View {
    func find(otherView: OtherView?) -> View? {
        if otherView == nil {
            return nil
        }
        if let v: View = otherView as? View {
            return v
        }
        if let sv: String = otherView as? String {
            if sv == SelfViewName || sv == self.name {
                return self
            }
            for child in self.subviews {
                if child.name == sv {
                    return child
                }
            }
            if sv == ParentViewName || sv == self.superview!.name {
                return self.superview!
            }
            for ch in self.superview!.subviews {
                if ch.name == sv {
                    return ch
                }
            }
        }
        fatalError("无效的other view ")
    }
}

public typealias ConstraintAttr = NSLayoutConstraint.Attribute
public typealias ConstraintRel = NSLayoutConstraint.Relation

public struct ConstraintKey: Hashable {
    public let attr: NSLayoutConstraint.Attribute
    public let rel: NSLayoutConstraint.Relation
}

public extension NSLayoutConstraint {
    var key: ConstraintKey {
        ConstraintKey(attr: self.firstAttribute, rel: self.relation)
    }
}

public extension View {


    //resist larger than intrinsic content size
    @discardableResult
    func stretchContent(_ axis: LayoutAxis) -> Self {
        setContentHuggingPriority(LayoutPriority(rawValue: LayoutPriority.defaultLow.rawValue - 1), for: axis)
        return self
    }

    //resist smaller than intrinsic content size
    @discardableResult
    func keepContent(_ axis: LayoutAxis) -> Self {
        setContentCompressionResistancePriority(LayoutPriority(rawValue: LayoutPriority.defaultHigh.rawValue + 1), for: axis)
        return self
    }

//    #if os(macOS)
    @available(macOS 11.0, *)
    @discardableResult
    func topAnchorParentSafeArea() -> Self {
        self.topAnchor.constraint(equalTo: self.superview!.safeAreaLayoutGuide.topAnchor).isActive = true
        return self
    }

    @available(macOS 11.0, *)
    @discardableResult
    func bottomAnchorParentSafeArea() -> Self {
        self.bottomAnchor.constraint(equalTo: self.superview!.safeAreaLayoutGuide.bottomAnchor).isActive = true
        return self
    }

//    #endif
}

public enum GravityX: Int {
    case none = 0
    case left
    case right
    case center
    case fill
}

public enum GravityY: Int {
    case none = 0
    case top
    case bottom
    case center
    case fill
}


extension View {

    fileprivate func processWrapOnPropChanged() {
        let sz = wrapSizeOf(view: self, self.measureSize)
        if let widthItem = self.constraintParams[ConstraintKey(attr: .width, rel: .equal)], widthItem.wrap {
            widthItem.constant = sz.width
            widthItem.install(self)
        }
        if let heightItem = self.constraintParams[ConstraintKey(attr: .height, rel: .equal)], heightItem.wrap {
            heightItem.constant = sz.height
            heightItem.install(self)
        }

        self.superview?.setNeedsLayout()
        if !self.subviews.isEmpty {
            self.setNeedsLayout()
        }
        self.setNeedsDisplay()
    }

}

public extension View {
    var layoutFixWidth: CGFloat? {
        switch superview {
        case is LinearLayout:
            let p = self.linearParams
            if p.width > 0 {
                return p.width
            }
        case is RelativeLayout:
            let p = self.relativeParams.conditions.first {
                $0.prop == .width && $0.otherView == nil
            }
            if p != nil {
                if p!.constant > 0 {
                    return p!.constant
                }
            }

        default:
            if let w = self.constraintParams[ConstraintKey(attr: .width, rel: .equal)], !w.wrap {
                if w.constant > 0 && w.otherView == nil && w.otherName == nil {
                    return w.constant
                }
            }
        }

        return nil
    }
    var layoutFixHeight: CGFloat? {
        switch superview {
        case is LinearLayout:
            let p = self.linearParams
            if p.height > 0 {
                return p.height
            }
        case is RelativeLayout:
            let p = self.relativeParams.conditions.first {
                $0.prop == .height && $0.otherView == nil
            }
            if p != nil {
                if p!.constant > 0 {
                    return p!.constant
                }
            }
        default:
            if let h = self.constraintParams[ConstraintKey(attr: .height, rel: .equal)], !h.wrap {
                if h.constant > 0 && h.otherView == nil && h.otherName == nil {
                    return h.constant
                }
            }
        }
        return nil
    }
    var measureSize: Size {
        var toSize = Size(self.layoutFixWidth ?? -1100, self.layoutFixHeight ?? -1100)
        if toSize.width > 0 && toSize.height > 0 {
            return toSize
        }
        if toSize.width < 0 {
            var w = superview?.frame.width ?? 0
            if w == 0 {
                w = UIScreen.main.bounds.width
            }
            toSize.width = w
        }
        if toSize.height < 0 {
            //TODO 10000->0
            toSize.height = 10000
        }
        return toSize
    }
}

@objcMembers
public class BaseLayout: View {

    public override var intrinsicContentSize: CGSize {
        contentSize
    }

    dynamic public private  (set) var contentSize: CGSize = .zero {
        didSet {
            if oldValue != contentSize {
                invalidateIntrinsicContentSize()
                processScroll()
                self.processWrapOnPropChanged()
            }
        }
    }

    public override class var requiresConstraintBasedLayout: Bool {
        true
    }

    public override func sizeThatFits(_ size: CGSize) -> CGSize {
        let toSize = measureSize
        return layoutChildren(toSize)
    }


    public override func addSubview(_ view: View) {
        view.translatesAutoresizingMaskIntoConstraints = false
//        view.autoresizesSubviews = false
        super.addSubview(view)
    }

    #if os(iOS)
    public override func didMoveToSuperview() {
        super.didMoveToSuperview()
        processScroll()
    }
    #else
    public override func viewDidMoveToSuperview() {
        super.viewDidMoveToSuperview()
        processSuper()
    }
    #endif

    private func processScroll() {
        if let pv = self.superview as? XScrollView {
            #if os(iOS)
            pv.contentSize = contentSize
            #endif
        }

    }

    #if os(iOS)
    public override func layoutSubviews() {
        super.layoutSubviews()
        self.contentSize = layoutChildren(nil)
    }
    #else
    public override func layout() {
        super.layout()
        self.contentSize = layoutChildren(nil)
    }
    #endif

    @discardableResult
    internal func layoutChildren(_ calcSize: CGSize?) -> CGSize {
        fatalError("NOT implement")
    }

    public override func didAddSubview(_ subview: UIView) {
        super.didAddSubview(subview)
    }

}

func wrapSizeOf(view: View, _ size: CGSize) -> CGSize {
    view.fitSize(size)
}


public typealias NeedLayoutCallback = (View) -> Void

public protocol WrapableView {
    var observeProps: [String] { get }
}

public extension View {


    func needLayoutOnPropChanged() {
        switch self {
        case is UILabel:
            self.postLayoutOn(prop: ["text", "font", "attributedText", "numberOfLines"])
        case is UITextField:
            self.postLayoutOn(prop: ["text", "font", "attributedText"])
        case is UITextView:
            self.postLayoutOn(prop: ["text", "font", "attributedText"])
        case is UIImageView:
            self.postLayoutOn(prop: ["image"])
        case is UIButton:
            self.postLayoutOn(prop: ["titleLabel.font", "titleLabel.text", "titleLabel.attributedText",
                                     "titleLabel.numberOfLines", "imageView.image",
                                     "contentEdgeInsets", "titleEdgeInsets", "imageEdgeInsets"])
        case is WrapableView:
//            logd("WrappleView!")
            self.postLayoutOn(prop: (self as! WrapableView).observeProps)

        default:
            break
        }
    }

    fileprivate var layoutPropList: HashSet<String> {
        ensureAttr(key: "layoutPropList") {
            HashSet<String>()
        }
    }


    fileprivate func postLayoutOn(prop: [String]) {
        let ls = self.layoutPropList
        for k in prop {
            if !ls.contains(k) {
                ls.insert(k)
                addObserver(LayoutObserver.inst, forKeyPath: k, options: [.old, .new], context: nil)
            }
        }
    }
}

fileprivate class LayoutObserver: NSObject {

    fileprivate static let inst = LayoutObserver()

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {

        if let v = object as? View {
            Task.fore {
                v.processWrapOnPropChanged()
            }
        }
    }

}
